
/**
 * @file pcmfile.c
 * raw PCM decoder
 */

#include "common.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "pcmfile.h"
#include "bswap.h"

/**
 * Reads a 2-byte big-endian word from the input stream
 */
static inline uint16_t read2be(PcmFile *pf)
{
    uint16_t x;
    if (byteio_read(&x, 2, &pf->io) != 2)
        return 0;
    pf->filepos += 2;
    return be2me_16(x);
}

int pcmfile_init(PcmFile *pf, FILE *fp, enum PcmSampleFormat read_format,
                 int file_format)
{
    if (pf == NULL || fp == NULL) {
        fprintf(stderr, "null input to pcmfile_init()\n");
        return -1;
    }

    pf->read_to_eof = 0;
    pf->file_format = file_format;
    pf->read_format = read_format;

    // attempt to get file size
    pf->file_size = 0;
    pf->seekable = 0;
#ifdef _WIN32
    // in Windows, don't try to detect seeking support for stdin
    if (fp != stdin) {
        pf->seekable = !fseek(fp, 0, SEEK_END);
    }
#else
    pf->seekable = !fseek(fp, 0, SEEK_END);
#endif
    if (pf->seekable) {
        // TODO: portable 64-bit ftell
        long fs = ftell(fp);
        // ftell should return an error if value cannot fit in return type
        if (fs < 0) {
            fprintf(stderr, "Warning, unsupported file size.\n");
            pf->file_size = 0;
        } else {
            pf->file_size = (uint64_t)fs;
        }
        fseek(fp, 0, SEEK_SET);
    }
    pf->filepos = 0;
    if (byteio_init(&pf->io, fp)) {
        fprintf(stderr, "error initializing byte buffer\n");
        return -1;
    }

    // detect file format if not specified by the user
    pcmfile_register_all_formats();
    if (pf->file_format == PCM_FORMAT_UNKNOWN) {
        uint8_t probe_data[12];
        byteio_peek(probe_data, 12, &pf->io);
        pf->pcm_format = pcmfile_probe_format(probe_data, 12);
        pf->file_format = pf->pcm_format->format;
    } else {
        pf->pcm_format = pcmfile_find_format(pf->file_format);
    }
    if (pf->pcm_format == NULL) {
        fprintf(stderr, "unable to detect file format\n");
        return -1;
    }

    // initialize format
    if (pf->pcm_format->init && pf->pcm_format->init(pf))
        return -1;

    return 0;
}

void pcmfile_close(PcmFile *pf)
{
    byteio_close(&pf->io);
}

void pcmfile_print(PcmFile *pf, FILE *st)
{
    const char *type, *chan, *fmt, *order;
    if (st == NULL || pf == NULL)
        return;
    type = "?";
    chan = "?-channel";
    fmt = "unknown";
    order = "";
    if (pf->sample_type == PCM_SAMPLE_TYPE_INT) {
        if (pf->source_format == PCM_SAMPLE_FMT_U8)
            type = "Unsigned";
        else
            type = "Signed";
    } else if (pf->sample_type == PCM_SAMPLE_TYPE_FLOAT) {
        type = "Floating-point";
    } else {
        type = "[unsupported type]";
    }
    if (pf->ch_mask & 0x08) {
        switch (pf->channels-1) {
            case 1: chan = "1.1-channel"; break;
            case 2: chan = "2.1-channel"; break;
            case 3: chan = "3.1-channel"; break;
            case 4: chan = "4.1-channel"; break;
            case 5: chan = "5.1-channel"; break;
            default: chan = "multi-channel with LFE"; break;
        }
    } else {
        switch (pf->channels) {
            case 1: chan = "mono"; break;
            case 2: chan = "stereo"; break;
            case 3: chan = "3-channel"; break;
            case 4: chan = "4-channel"; break;
            case 5: chan = "5-channel"; break;
            case 6: chan = "6-channel"; break;
            default: chan = "multi-channel"; break;
        }
    }
    if (pf->pcm_format)
        fmt = pf->pcm_format->long_name;
    if (pf->source_format > PCM_SAMPLE_FMT_S8) {
        switch (pf->order) {
            case PCM_BYTE_ORDER_LE: order = "little-endian"; break;
            case PCM_BYTE_ORDER_BE: order = "big-endian"; break;
        }
    } else {
        order = "\b";
    }
    fprintf(st, "%s %s %d-bit %s %d Hz %s\n", fmt, type, pf->bit_width, order,
            pf->sample_rate, chan);
}

int pcm_get_default_ch_mask(int channels)
{
    static const int nch_to_mask[6] = {
        0x04,   // mono         (1/0)
        0x03,   // stereo       (2/0)
        0x103,  // 3.0 surround (2/1)
        0x107,  // 3/1 surround (3/1)
        0x37,   // 5.0 surround (3/2)
        0x3F    // 5.1 surround (3/2+LFE)
    };
    if (channels < 1 || channels > 6)
        return 0;
    return nch_to_mask[channels-1];
}

uint32_t pcmfile_read4le(PcmFile *pf)
{
    uint32_t x;
    if (byteio_read(&x, 4, &pf->io) != 4)
        return 0;
    pf->filepos += 4;
    return le2me_32(x);
}

uint16_t pcmfile_read2le(PcmFile *pf)
{
    uint16_t x;
    if (byteio_read(&x, 2, &pf->io) != 2)
        return 0;
    pf->filepos += 2;
    return le2me_16(x);
}

uint64_t pcmfile_read8be(PcmFile *pf)
{
    uint64_t x;
    if (byteio_read(&x, 8, &pf->io) != 8)
        return 0;
    pf->filepos += 8;
    return be2me_64(x);
}

double pcmfile_read_dbl_be(PcmFile *pf)
{
    union {
        uint64_t i;
        double d;
    } x;
    x.i = pcmfile_read8be(pf);
    return x.d;
}

uint16_t pcmfile_read2be(PcmFile *pf)
{
    uint16_t x;
    if (byteio_read(&x, 2, &pf->io) != 4)
        return 0;
    pf->filepos += 2;
    return be2me_16(x);
}

uint32_t pcmfile_read4be(PcmFile *pf)
{
    uint32_t x;
    if (byteio_read(&x, 4, &pf->io) != 4)
        return 0;
    pf->filepos += 4;
    return be2me_32(x);
}

ExtFloat pcmfile_read_ext(PcmFile *pf)
{
    ExtFloat x;
    if (byteio_read(&x, 10, &pf->io) != 10) {
        memset(&x, 0, sizeof(x));
        return x;
    }
    pf->filepos += 10;
    return x;
}

uint64_t pcmfile_read_ext_u64(PcmFile *pf)
{
    ExtFloat ext = pcmfile_read_ext(pf);
    uint64_t m = 0;
    int e, i;

    for (i = 0; i < 8; i++)
        m = (m<<8) + ext.mantissa[i];
    e = (((int)ext.exponent[0]&0x7f)<<8) | ext.exponent[1];
    if (e == 0x7fff && m)
        return 0.0; // really should be NaN, but simplifying for portability
    e -= 16383 + 63;        /* In IEEE 80 bits, the whole (i.e. 1.xxxx)
                             * mantissa bit is written as opposed to the
                             * single and double precision formats */
    if (ext.exponent[0]&0x80)
        m = -m;
    if (e > 0)
        m <<= e;
    else if(e < 0)
        m >>= -e;
    return m;
}
